\cleardoublepage
\chapter{中期报告}
\section{项目概况}

在通信技术的进步下，云端化逐渐成为了很多高负载应用的解决方案。本项目正式为解决实际生产中遇到的相应的云端化问题而被提出：在局域网中运行的医学影像渲染系统服务器需要在接收到用户的请求数据后对模型进行渲染，并实时将渲染结果返回给客户端；需要同时满足上百台客户机的需求。本项目针对此场景中 Windows 服务器与客户端的数据交换，以 WebSocket 协议作为数据交换的载体，基于 Windows 上的异步 I/O API，设计并开发出一套在保证响应速度的同时尽量降低 CPU 和内存占用的服务器/客户端 WebSocket 框架。

\par

WebSocket 协议在 2011 年被 IETF 以 RFC 6455 标准化。它被设计为一个在 HTTP 1.1 基础上的应用层全双工计算机通信协议，用来解决在服务器和客户端需要频繁进行数据交换场合时 HTTP 协议出现的不足。因为 HTTP 协议中仅允许客户端主动向服务器发起请求，所以在遇到服务器需要主动向客户端发送数据时只能使用轮询等方法，让客户端主动向服务器请
求数据等待回应，从而增加了网络通讯的开销以及服务器的压力。相较于 HTTP ，WebSocket 能够对底层建立起的 TCP 连接进行更充分的利用：WebSocket 协议在使用 HTTP 1.1 协议进行握手之后直接利用建立起的 TCP 连接进行双工的通信，减少了额外的连接建立/关闭开销。

\par

本项目使用了 Windows 平台上的异步 I/O 框架：I/O 完成端口 作为底层的数据交互实现。I/O 完成端口是一种用于 Windows NT 3.5 以及更高版本等操作系统中同时执
行多个异步 I/O 操作的 API，当线程创建 I/O 完成端口时，操作系统会维护该完成端口连接的文件设备、I/O 完成队列（FIFO）、等待线程队列（LIFO)等相关数据，操作系统可以根据压力动态调整 I/O 线程的数量，防止过多的上下文切换带来不必要的性能开销。

\par

本项目的实现主要有以下几个要点：

\begin{itemize}[topsep=0pt]
    \setlength{\itemindent}{0pt}
          \setlength{\itemsep}{0pt}
          \setlength{\parsep}{0pt}
          \setlength{\parskip}{0pt}
    \item 研究 Windows 上的异步 I/O 模型 IOCP 以及相关的线程池模型，Windows 平台网络编程 API，并将这些 API 封装为现代 C++ 风格的接口，
    \item 实现合理的 C++ 日志库以提供清晰的日志服务
    \item 实现 Windows 上的高性能 Socket 服务器，以及简单的测试用客户端
    \item 实现 WebSocket 协议的包解析以及处理部分
    \item 实现 WebSocket 服务器与客户端之间的通信
    \item 实现服务器与客户端的断线检测功能
    \item 通过 nginx 进行反向代理实现 WebSocket Secure，并为客户端添加 SSL 的支持
    \item 对服务器进行性能以及可行性测试
\end{itemize}

\section{工作进展情况}

目前项目的实现进展顺利，已经完成了以下部分模块的实现以及功能测试

\subsection{基于 IOCP 的 Socket 服务器实现}

项目中使用了 Windows 在 Vista 之后推出的全新的线程池 API 来作为 I/O 完成端口接入点。使用这套 API 时，我们可以对于一个已有的设备（文件，Socket 句柄等）创建一个 ThreadPoolIO （线程池 I/O）结构 ，并传入相关 I/O 完成事件的处理函数。操作系统会根据设备上的 I/O 压力动态调整处理 I/O 的线程数量。对于服务器端的每一个 Socket 句柄，我们维护一个 Socket 上下文类，并在其中保存对于每个 Socket 创建的线程池 I/O 结构，以及通过异步 I/O 接口投递的相关 I/O 上下文等信息。所有的 I/O 操作信息都通过 I/O 上下文类保存。I/O 上下文类继承自 Windows API 中的 OVERLAPPED （重叠结构），由 Socket 上下文类创建并持有，它将保存 I/O 操作的类型以及相关的数据缓冲区。在使用 Windows 的 ThreadPoolIO 时，将 I/O 上下文的指针作为重叠结构的指针传入。

\par

在 Socket 服务器启动时，创建 Socket 绑定端口开始监听，同时使用 AcceptEx 来在相应的 Socket 上下文里保存的线程池 I/O 上投递复数的异步的 accept 请求。工作线程在收到 accept 操作完成的事件时，会调用创建线程池 I/O 时传入的函数。该函数会为接收到的连接创建新的 Socket 上下文和线程池 I/O，并投递异步的 recv 请求。在获得 recv 操作完成的事件时，将获得的数据进行处理，并继续在该 Socket 上投递异步 recv 请求。

\par

整体而言，一个 Socket 服务器类持有着服务器 Socket 和与客户端完成连接的 Socket 所对应的所有 Socket 上下文的实例，其中服务器 Socket 上下文的生命周期与服务器相同，其余的 Socket 上下文与每个 Socket 连接的状态相关联。为了保证本项目的代码可重用和可扩展性，我们为 Socket 服务器设置了 accept，recv，send，close 相应 Socket 行为的回调函数，并可以在此基础上实现各类应用层协议，包括本项目所需要的 WebSocket 应用。

\subsection{WebSocket 协议部分实现}

WebSocket 协议的握手部分是基于 HTTP 1.1 之上的，我们需要先按照 HTTP 的请求和回应格式将双方握手部分的数据进行解析，然后根据 WebSocket 协议检查双方 HTTP Message 中相应的字段是否为规定的值，如果有一方检查失败则连接不成立。考虑到生成的二进制文件体积问题，我们采用了 Header-Only 的密码学相关算法实现用以 WebSocket 握手。在连接完成后双方开始基于 WebSocket 帧的数据交换。

\par

Socket 服务器和客户端所接收的只有字节流，我们需要对字节流进行解析成完整的 WebSocket 帧。由于 WebSocket 帧头部本身都是不定长度的，所以我们需要通过前两个字节来确定帧头部的长度字段的长度，来判断帧头部是否接收完毕。在确认接收到的数据中已经包含一个完整的数据帧时，根据其 Opcode 决定相应的行为：例如对连接对方传来的 Ping 控制帧时需要回应一个相同内容的 Pong 控制帧。如果剩余的数据无法形成一个完整的帧，则将数据添加至缓冲区末尾，等待下次接收时重新检查。

\par

WebSocket 的关闭流程也需要通过控制帧来实现。当连接双方中的一方需要关闭连接时，需要先向对方发送一个 Opcode 为 Close 的控制帧，如果该帧包含有数据的话，则前两个字节代表连接关闭的原因或状态。另一方在接收到对方的关闭帧后要在返回一个 Close 帧后关闭 WebSocket 连接，而关闭的发起方在超时或收到对方返回的关闭帧后
关闭相应的 Socket 连接。

\par

考虑到服务器和客户端的对称性，我们使用单独的 WebSocket 接收数据处理类来管理 WebSocket 帧的解析和行为，服务器和客户端只需要传入不同的帧处理函数即可。

\subsection{断线检测实现}

考虑到 WebSocket 服务器可能需要通过代理来提供服务，所以不能直接通过 Socket 连接来检查客户端是否断开。WebSocket 协议提供了 Ping 和 Pong 两个控制帧来提供这种功能，我们在客户端上设置了每隔一段时间便发送一个 Ping 控制帧的线程，同时服务器和客户端添加了守护线程来检查现在与上一次接收来自对方的消息之间距离的时间是否超过了认定为连接超时的阈值，在超过一定次数后会清除掉此 WebSocket 连接以及其下的 Socket 连接。

\par

此外，发出 Ping 的一方可以在 Ping 控制帧的数据部分添加发送时的时间戳等数据，依赖于 WebSocket 规定的 Pong 需要返还 Ping 相同数据的约定，用此数据测出客户端与服务器之间的延时，用来调整相应的数据发送策略。

\subsection{成果展示}

下面我们通过一个 WebSocket 服务器和客户端的简单实例来进行功能展示。

\begin{enumerate}[topsep=0pt]
    \setlength{\itemindent}{0pt}
          \setlength{\itemsep}{0pt}
          \setlength{\parsep}{0pt}
          \setlength{\parskip}{0pt}
    \item 启动 WebSocket 服务器，开始监听指定的端口（图 \ref{wsserver-start}）
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=14cm]{midcheck/wsserver-start.png}
              \caption{WebSocket 服务器启动}
              \label{wsserver-start}
          \end{figure}
    \item 启动 WebSocket 客户端，向服务器发出握手请求（图 \ref{wsclient-connect}），服务器接收并回应完成 WebSocket 连接（图 \ref{wsserver-connect}）
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=13cm]{midcheck/wsclient-connect.png}
              \caption{WebSocket 客户端发起连接}
              \label{wsclient-connect}
          \end{figure}
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=14cm]{midcheck/wsserver-connect.png}
              \caption{WebSocket 服务器接收连接}
              \label{wsserver-connect}
          \end{figure}
    \item 客户端定时向服务器发送 Ping 数据包，服务器接收并回应（图 \ref{wsserver-pong}），客户端根据返回的时间计算时延 （图 \ref{wsclient-ping})
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=14cm]{midcheck/wsserver-pong.png}
              \caption{WebSocket 服务器返回 Pong 数据帧}
              \label{wsserver-pong}
          \end{figure}
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=12cm]{midcheck/wsclient-ping.png}
              \caption{WebSocket 客户端计算时延}
              \label{wsclient-ping}
          \end{figure}
    \item 客户端先通过文件 I/O 读取图片，随即将数据分帧发送给服务器，服务器接收后将数据帧拼装并写入本地文件中（图 \ref{wsserver-recvfile})，更改后缀名后可以发现图片数据完整（图 \ref{wsserver-file}）
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=13cm]{midcheck/wsserver-recvfile.png}
              \caption{WebSocket 服务器接收文件}
              \label{wsserver-recvfile}
          \end{figure}
          \begin{figure}[htbp]
              \centering
              \includegraphics[width=14cm]{midcheck/wsserver-file.png}
              \caption{WebSocket 服务器接收的图片}
              \label{wsserver-file}
          \end{figure}
\end{enumerate}

\section{问题与建议}

目前尚未遇到阻碍进度的问题